{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "source": [
    "<table>\n",
    " <tr align=left><td><img align=left src=\"./images/CC-BY.png\">\n",
    " <td>Text provided under a Creative Commons Attribution license, CC-BY. All code is made available under the FSF-approved MIT license. (c) Kyle T. Mandli</td>\n",
    "</table>\n",
    "\n",
    "Note:  The presentation below largely follows part I in \"Finite Difference Methods for Ordinary and Partial Differential Equations\" by LeVeque (SIAM, 2007)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "init_cell": true,
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "outputs": [],
   "source": [
    "from __future__ import print_function\n",
    "\n",
    "%matplotlib inline\n",
    "import numpy\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Solving Boundary Value Problems"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## The Problem\n",
    "\n",
    "We want to solve an ODE (PDE) that instead of having initial conditions is contained to an interval and has values at the edges of the interval.  This naturally comes about when we consider spatial problems.  One of the simplest cases for this is the Poisson problem in one-dimension\n",
    "$$\n",
    "    u_{xx} = f(x)\n",
    "$$\n",
    "where we will use the short-hand\n",
    "$$\n",
    "    u_{xx} = \\frac{\\text{d}^2 u}{\\text{d} x^2} \\quad \\text{or} \\quad \\frac{\\partial^2 u}{\\partial x^2}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Note that due to the order of the derivative we require two conditions to solve this.  The simplest case where we are on the domain $x \\in [a,b]$ is to have conditions such that we require $u(a) = u_a$ and $u(b) = u_b$ and are commonly termed boundary value problems (BVP).  If these conditions are both at one end of the domain then we can actually phrase the ODE (PDE) again as an initial value problem (IVP).  So what do we need to do to solve these types of problems?  We will consider two approaches to this problem:\n",
    "\n",
    "1. Rephrase the BVP to an IVP and use our standard methods for ODEs.\n",
    "1. Use finite differences to represent the unknowns as a linear system and solve the resulting system."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## The Shooting Method\n",
    "\n",
    "The shooting method takes the approach that we want to use our ability to solve IVP problems and so tries to term the problem as a root finding problem for the higher order initial condition that we are not given.  This is best illustrated in an example."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Consider the problem\n",
    "$$\n",
    "    u_{xx} = -\\sin u\n",
    "$$\n",
    "with\n",
    "$$\n",
    "    x \\in [0, 2] \\quad \\text{and} \\quad u(0) = 0.0, \\quad u(2.0) = \\frac{\\pi}{2}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "We can rewrite this problem as a system of two ODEs as\n",
    "$$\n",
    "    v = \\begin{bmatrix} u \\\\ u_x \\end{bmatrix} \\quad \\text{and} \\quad v_x = \\begin{bmatrix} u_x \\\\ u_{xx} \\end{bmatrix} = \\begin{bmatrix} v_2 \\\\ -\\sin v_1 \\end{bmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "We know that we want $v_1(0) = 0$ but what do we use for $v_2(0)$?  Making an initial guess at $v_2(0)$ and solving the associated IVP ODE we can then find out what these initial conditions produces on the right boundary of the problem.  Using a root-finding approach (or minimization routine) we can write this procedure as\n",
    "$$\n",
    "    \\min_{v_2(0)} \\left | \\pi / 2 - v_1(2) \\right |\n",
    "$$\n",
    "where the parameter we vary is $v_2(0)$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "outputs": [],
   "source": [
    "# Basic Shooting Method solving u_xx = -sin(u)\n",
    "import scipy.integrate as integrate\n",
    "\n",
    "# Algorithm parameters\n",
    "TOLERANCE = 1e-8\n",
    "MAX_ITERATIONS = 100\n",
    "\n",
    "# Problem Statement\n",
    "a = 0.0\n",
    "b = 2.0\n",
    "N = 100\n",
    "x = numpy.linspace(a, b, N)\n",
    "u_a = 0.0\n",
    "u_b = numpy.pi / 2.0\n",
    "# RHS function\n",
    "def f(x, u):\n",
    "    return numpy.array([u[1], -numpy.sin(u[0])])\n",
    "\n",
    "# Initial guess\n",
    "# Slope at RHS\n",
    "u_prime_rhs = 1.0\n",
    "# Initial step size\n",
    "du_prime = 0.5\n",
    "\n",
    "# Plotting\n",
    "fig = plt.figure()\n",
    "fig.set_figwidth(fig.get_figwidth() * 2)\n",
    "axes = fig.add_subplot(1, 2, 1)\n",
    "\n",
    "# Main loop\n",
    "success = False\n",
    "u = numpy.empty((2, N))\n",
    "convergence = numpy.zeros(MAX_ITERATIONS)\n",
    "for n in range(MAX_ITERATIONS):\n",
    "    \n",
    "    # Initial condition\n",
    "    u[0, 0] = u_a\n",
    "    u[1, 0] = u_prime_rhs\n",
    "\n",
    "    # Construct integrator\n",
    "    integrator = integrate.ode(f)\n",
    "    integrator.set_integrator(\"dopri5\")\n",
    "    integrator.set_initial_value(u[:, 0])\n",
    "\n",
    "    # Compute solution - note that we are only producing the intermediate values\n",
    "    # for demonstration purposes\n",
    "    for (i, x_output) in enumerate(x[1:]):\n",
    "        integrator.integrate(x_output)\n",
    "        if not integrator.successful():\n",
    "            raise Exception(\"Integration Failed!\")\n",
    "        u[:, i + 1] = integrator.y\n",
    "\n",
    "    # Stopping Criteria\n",
    "    convergence[n] = numpy.abs(u[0, -1] - u_b)\n",
    "    if numpy.abs(u[0, -1] - u_b) < TOLERANCE:\n",
    "        success = True\n",
    "        break\n",
    "    else:\n",
    "        if u[0, -1] < u_b:\n",
    "            u_prime_rhs += du_prime\n",
    "        else:\n",
    "            u_prime_rhs -= du_prime\n",
    "            du_prime *= 0.5\n",
    "\n",
    "    axes.plot(x, u[0, :], 'b')\n",
    "    axes.plot(b, u_b, 'ro')\n",
    "\n",
    "axes.set_title(\"Shooting Method Iterations\")\n",
    "axes.set_xlabel(\"$x$\")\n",
    "axes.set_ylabel(\"$u(x)$\")\n",
    "\n",
    "axes = fig.add_subplot(1, 2, 2)\n",
    "n_range = numpy.arange(n)\n",
    "axes.semilogy(n_range, convergence[:n])\n",
    "axes.set_title(\"Convergence of Shooting Method\")\n",
    "axes.set_xlabel(\"step\")\n",
    "axes.set_ylabel(\"$|u(b) - U(b)|$\")\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "The tricky part of this procedure is coming up with the search criteria, i.e. coming up with the decision of how to change $v_2(0)$ with respect to the position of $v_2(2)$ compared to what we want $u(2)$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "In general any minimization routine can be used in a shooting method.  These approaches are generally very effective at approaching non-linear BVPs where the next method we will discuss is too expensive to perform."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Linear System Approach"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Formulation\n",
    "\n",
    "The second approach we will consider involves the formation of a system of equations to solve based on finite difference approximations.  Again let's consider an example problem where\n",
    "$$\n",
    "    u_{xx} = f(x)\n",
    "$$\n",
    "with the initial conditions $u(a) = u_a$ and $u(b) = u_b$.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "We know from our finite difference discussion that the second order centered difference approximation for the second derivative for a function $u(x)$ is\n",
    "$$\n",
    "    u_{xx} \\approx \\frac{u(x_{i-1}) - 2 u(x_i) + u(x_{i+1})}{\\Delta x^2}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "If we descretize the domain of the original BVP into $N$ points (not including the boundaries) such that\n",
    "$$\n",
    "    x_i = a + \\frac{b - a}{N+1} \\cdot i ~~~ \\text{where} ~~~ i = 1, \\ldots, N\n",
    "$$\n",
    "we can then write the finite difference approximation as a system of linear equations!  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "If for instance we take $N = 5$ then\n",
    "$$\\begin{aligned}\n",
    "    (U_{xx})_1 &\\approx \\frac{U_a - 2 U_1 + U_2}{\\Delta x^2} \\\\\n",
    "    (U_{xx})_2 &\\approx \\frac{U_1 - 2 U_2 + U_3}{\\Delta x^2} \\\\\n",
    "    (U_{xx})_3 &\\approx \\frac{U_2 - 2 U_3 + U_4}{\\Delta x^2} \\\\\n",
    "    (U_{xx})_4 &\\approx \\frac{U_3 - 2 U_4 + U_5}{\\Delta x^2} \\\\\n",
    "    (U_{xx})_5 &\\approx \\frac{U_4 - 2 U_5 + U_b}{\\Delta x^2} \\\\\n",
    "\\end{aligned}$$\n",
    "where we have used $U_a = u(a)$ and $U_b = u(b)$ as the boundary conditions.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Using these approximations to the derivatives we can then write the ODE as\n",
    "$$\n",
    "    \\frac{1}{\\Delta x^2}\\begin{bmatrix}\n",
    "    -2 &  1 &    &    &    \\\\\n",
    "     1 & -2 &  1 &    &    \\\\\n",
    "       &  1 & -2 &  1 &    \\\\\n",
    "       &    &  1 & -2 &  1 \\\\\n",
    "       &    &    &  1 & -2 \\\\\n",
    "    \\end{bmatrix} \\begin{bmatrix}\n",
    "        U_1 \\\\ U_2 \\\\ U_3 \\\\ U_4 \\\\ U_5\n",
    "    \\end{bmatrix} = \n",
    "    \\begin{bmatrix}\n",
    "        f(x_1) \\\\ f(x_2) \\\\ f(x_3) \\\\ f(x_4) \\\\ f(x_5) \\\\\n",
    "    \\end{bmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "notes"
    }
   },
   "source": [
    "Note that our previous example used for the shooting method is difficult in the current context as the unknown function is in the function $f$ so that we would need to actual solve a non-linear system of equations.  This is still possible in this context using an approach such as a Newton solver and has similar properties as the shooting method (although not as simple to implement)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Boundary Conditions\n",
    "\n",
    "This does not include the boundary conditions though.  We can add these values easily for Dirichlet boundary conditions by sending the values we know to the $b$ vector:\n",
    "$$\\begin{aligned}\n",
    "    \\frac{U_a - 2 U_1 + U_2}{\\Delta x^2} = f(x_1) &\\Rightarrow& \\frac{- 2 U_1 + U_2}{\\Delta x^2} = f(x_1) - \\frac{U_a}{\\Delta x^2} \\\\\n",
    "    \\frac{U_4 - 2 U_5 + U_b}{\\Delta x^2} = f(x_1) &\\Rightarrow& \\frac{U_4 - 2 U_5}{\\Delta x^2} = f(x_5) - \\frac{U_b}{\\Delta x^2}\n",
    "\\end{aligned}$$\n",
    "so that final system looks like\n",
    "$$\n",
    "    \\frac{1}{\\Delta x^2} \\begin{bmatrix}\n",
    "    -2 &  1 &    &    &    \\\\\n",
    "     1 & -2 &  1 &    &    \\\\\n",
    "       &  1 & -2 &  1 &    \\\\\n",
    "       &    &  1 & -2 &  1 \\\\\n",
    "       &    &    &  1 & -2 \\\\\n",
    "    \\end{bmatrix} \\begin{bmatrix}\n",
    "        U_1 \\\\ U_2 \\\\ U_3 \\\\ U_4 \\\\ U_5\n",
    "    \\end{bmatrix} = \n",
    "    \\begin{bmatrix}\n",
    "        f(x_1) - \\frac{U_a}{\\Delta x^2} \\\\ f(x_2) \\\\ f(x_3) \\\\ f(x_4) \\\\ f(x_5) - \\frac{U_b}{\\Delta x^2} \\\\\n",
    "    \\end{bmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Example\n",
    "\n",
    "Want to solve the BVP\n",
    "$$\n",
    "    u_{xx} = e^x, \\quad x \\in [0, 1] \\quad \\text{with} \\quad u(0) = 0.0, \\text{ and } u(1) = 3\n",
    "$$\n",
    "via the construction of a linear system of equations."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "\\begin{align*}\n",
    "    u_{xx} &= e^x \\\\\n",
    "    u_x &= A + e^x \\\\\n",
    "    u &= Ax + B + e^x\\\\\n",
    "    u(0) &= B + 1 = 0 \\Rightarrow B = -1 \\\\\n",
    "    u(1) &= A - 1 + e^{1} = 3 \\Rightarrow A = 4 - e\\\\ \n",
    "    ~\\\\\n",
    "    u(x) &= (4 - e) x - 1 + e^x\n",
    "\\end{align*}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "outputs": [],
   "source": [
    "# Problem setup\n",
    "a = 0.0\n",
    "b = 1.0\n",
    "u_a = 0.0\n",
    "u_b = 3.0\n",
    "f = lambda x: numpy.exp(x)\n",
    "u_true = lambda x: (4.0 - numpy.exp(1.0)) * x - 1.0 + numpy.exp(x)\n",
    "\n",
    "# Descretization\n",
    "N = 10\n",
    "x_bc = numpy.linspace(a, b, N + 2)\n",
    "x = x_bc[1:-1]\n",
    "delta_x = (b - a) / (N + 1)\n",
    "\n",
    "# Construct matrix A\n",
    "A = numpy.zeros((N, N))\n",
    "diagonal = numpy.ones(N) / delta_x**2\n",
    "A += numpy.diag(diagonal * -2.0, 0)\n",
    "A += numpy.diag(diagonal[:-1], 1)\n",
    "A += numpy.diag(diagonal[:-1], -1)\n",
    "\n",
    "# Construct RHS\n",
    "b = f(x)\n",
    "b[0] -= u_a / delta_x**2\n",
    "b[-1] -= u_b / delta_x**2\n",
    "\n",
    "# Solve system\n",
    "U = numpy.empty(N + 2)\n",
    "U[0] = u_a\n",
    "U[-1] = u_b\n",
    "U[1:-1] = numpy.linalg.solve(A, b)\n",
    "\n",
    "# Plot result\n",
    "fig = plt.figure()\n",
    "axes = fig.add_subplot(1, 1, 1)\n",
    "axes.plot(x_bc, U, 'o', label=\"Computed\")\n",
    "axes.plot(x_bc, u_true(x_bc), 'k', label=\"True\")\n",
    "axes.set_title(\"Solution to $u_{xx} = e^x$\")\n",
    "axes.set_xlabel(\"x\")\n",
    "axes.set_ylabel(\"u(x)\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "If we instead have Neumann boundary conditions it is no longer clear how to handle the boundary conditions using the above approach.  Instead a **ghost cell** approach is often used.  These **ghost cells** are added unknowns that represent the boundary values that we actually know.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "For instance, if we had the BVP\n",
    "$$\n",
    "    u_{xx} = e^x, \\quad x \\in [-1, 1] \\quad \\text{with} \\quad u(-1) = 3, \\text{ and } u_x(1) = -5\n",
    "$$\n",
    "then we could keep the boundary values in the vector of unknowns so that now\n",
    "$$\n",
    "    U = \\begin{bmatrix} U_0 \\\\ U_1 \\\\ \\vdots \\\\ U_N \\\\ U_{N+1} \\end{bmatrix}\n",
    "$$\n",
    "where here $U_0$ and $U_{N+1}$ are actually the boundary points.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "The matrix $A$ is then modified to have the appropriate relationships.  In this case the left boundary condition leads to\n",
    "$$\n",
    "    A = \\begin{bmatrix}\n",
    "  1 &    &    &    &    &    \\\\\n",
    "  \\frac{1}{\\Delta x^2} & \\frac{-2}{\\Delta x^2} &  \\frac{1}{\\Delta x^2} &    &    &    \\\\\n",
    "    &  \\frac{1}{\\Delta x^2} & \\frac{-2}{\\Delta x^2} &  \\frac{1}{\\Delta x^2} &    &    \\\\\n",
    "    & & \\ddots & \\ddots & \\ddots\n",
    "    \\end{bmatrix} \\quad \\text{and} \\quad b = \\begin{bmatrix}\n",
    "        u(a) \\\\ f(x_1) \\\\ f(x_2) \\\\ \\vdots\n",
    "    \\end{bmatrix}\n",
    "$$\n",
    "which multiplied out simply gives\n",
    "$$\n",
    "    U_0 = u(-1) = 3.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "For the right boundary condition we can use the second order backward finite difference approximation for the first derivative\n",
    "$$\n",
    "    u_x(b) \\approx \\frac{3 U_{N+1} - 4 U_{N} + U_{N - 1}}{2.0 \\Delta x} = -5\n",
    "$$\n",
    "which can be incorporated into the matrix $A$ and vector $b$ as\n",
    "$$\n",
    "    A =  \\begin{bmatrix}\n",
    "     \\ddots & \\ddots & \\ddots &    &    \\\\\n",
    "            & \\frac{1}{\\Delta x^2} &     \\frac{-2}{\\Delta x^2}&  \\frac{1}{\\Delta x^2} &    \\\\\n",
    "            &        &      \\frac{1}{\\Delta x^2} & \\frac{-2}{\\Delta x^2} &  \\frac{1}{\\Delta x^2} \\\\\n",
    "            &        &      \\frac{1}{2 \\Delta x} &  \\frac{-4}{2 \\Delta x} &  \\frac{3}{2 \\Delta x} \\\\\n",
    "    \\end{bmatrix} ~~~~ \\text{and} ~~~~ b = \\begin{bmatrix}\n",
    "        \\vdots \\\\ f(x_N) \\\\ u_x(b)\n",
    "    \\end{bmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "All together the new system looks like\n",
    "$$\n",
    "    \\begin{bmatrix}\n",
    "     1 &    &    &    &    &    \\\\\n",
    "     \\frac{1}{\\Delta x^2} & \\frac{-2}{\\Delta x^2} &  \\frac{1}{\\Delta x^2} &    &    &    \\\\\n",
    "       &  \\ddots & \\ddots &  \\ddots &    \\\\\n",
    "       &    & \\frac{1}{\\Delta x^2} & \\frac{-2}{\\Delta x^2} &  \\frac{1}{\\Delta x^2} \\\\\n",
    "            &        &      \\frac{1}{2 \\Delta x} &  \\frac{-4}{2 \\Delta x} &  \\frac{3}{2 \\Delta x} \\\\\n",
    "    \\end{bmatrix} \\begin{bmatrix}\n",
    "        U_0 \\\\ U_1 \\\\ \\vdots \\\\ U_N \\\\ U_{N+1}\n",
    "    \\end{bmatrix} = \n",
    "    \\begin{bmatrix}\n",
    "        u(a) \\\\ f(x_1) \\\\ \\vdots \\\\ f(x_N) \\\\ u_x(b)\n",
    "    \\end{bmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Example\n",
    "\n",
    "Want to solve the BVP\n",
    "$$\n",
    "    u_{xx} = e^x, \\quad x \\in [-1, 1] \\quad \\text{with} \\quad u(-1) = 3.0, \\text{ and } u_x(1) = -5.0\n",
    "$$\n",
    "via the construction of a linear system of equations.\n",
    "\n",
    "First find the true solution and then compute the solution."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "\\begin{align*}\n",
    "    u(x) &= A x + B + e^x \\\\\n",
    "    u_x(1) &= A + e^1 = -5 \\Rightarrow A = -5 - e \\\\\n",
    "    u(-1) &= (5 + e) + B + e^{-1} = 3 \\Rightarrow B = 3 - 5 - e - e^{-1} = -(2 + e + e^{-1}) \\\\\n",
    "    ~\\\\\n",
    "    u(x) &= -(5 + e) x -(2 + e + e^{-1}) + e^{x}\n",
    "\\end{align*}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "outputs": [],
   "source": [
    "# Problem setup\n",
    "a = -1.0\n",
    "b = 1.0\n",
    "u_a = 3.0\n",
    "u_x_b = -5.0\n",
    "f = lambda x: numpy.exp(x)\n",
    "u_true = lambda x: -(5.0 + numpy.exp(1.0)) * x - (2.0 + numpy.exp(1.0) + numpy.exp(-1.0)) + numpy.exp(x)\n",
    "\n",
    "# Descretization\n",
    "N = 10\n",
    "x_bc = numpy.linspace(a, b, N + 2)\n",
    "x = x_bc[1:-1]\n",
    "delta_x = (b - a) / (N + 1)\n",
    "\n",
    "# Construct matrix A\n",
    "A = numpy.zeros((N + 2, N + 2))\n",
    "diagonal = numpy.ones(N + 2) / delta_x**2\n",
    "A += numpy.diag(diagonal * -2.0, 0)\n",
    "A += numpy.diag(diagonal[:-1], 1)\n",
    "A += numpy.diag(diagonal[:-1], -1)\n",
    "\n",
    "# Construct RHS\n",
    "b = f(x_bc)\n",
    "\n",
    "# Boundary conditions\n",
    "A[0, 0] = 1.0\n",
    "A[0, 1] = 0.0\n",
    "A[-1, -1] = 3.0 / (2.0 * delta_x)\n",
    "A[-1, -2] = -4.0 / (2.0 * delta_x)\n",
    "A[-1, -3] = 1.0 / (2.0 * delta_x)\n",
    "\n",
    "b[0] = u_a\n",
    "b[-1] = u_x_b\n",
    "\n",
    "# Solve system\n",
    "U = numpy.empty(N + 2)\n",
    "U = numpy.linalg.solve(A, b)\n",
    "\n",
    "# Plot result\n",
    "fig = plt.figure()\n",
    "axes = fig.add_subplot(1, 1, 1)\n",
    "axes.plot(x_bc, U, 'o', label=\"Computed\")\n",
    "axes.plot(x_bc, u_true(x_bc), 'k', label=\"True\")\n",
    "axes.set_title(\"Solution to $u_{xx} = e^x$\")\n",
    "axes.set_xlabel(\"x\")\n",
    "axes.set_ylabel(\"u(x)\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "## Ways to Solve $A u = f$\n",
    "\n",
    "We have proposed solving the linear system $A u = f$ which we have implemented naively above with the `numpy.linalg.solve` command but perhaps given the special structure of $A$ here that we can do better.   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Direct Methods (Gaussian Elimination)\n",
    "\n",
    "We could use Gaussian elimination to solve the system (or some factorization) which leads to a solution in a finite number of steps.  For large, sparse methods however these direct solvers are much more expensive in general over iterative solvers.  As was discussed for eigenproblems, iterative solvers start with an initial guess and try to improve on that guess. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Consider a 3D Poisson Problem:\n",
    " - Discretize using $100 \\times 100 \\times 100 = 10^6$ unknowns\n",
    " - Gaussian elimination requires $\\mathcal{O}(N^3)$ operations\n",
    " - Solving this system would take $10^{18}$ floating point operations to complete\n",
    " \n",
    "**How long?**\n",
    "\n",
    "Today's computer is $\\approx 100$ gigaflops (floating point operations per second) - $10^{11}$ flops / second.  We would be waiting 115 days for the solve to finish!\n",
    "\n",
    "**Memory?**\n",
    "\n",
    "Matrix requires $N^2$ memory locations ($N = 10^6$ here).  Single precision floating point storage (4-bytes per number) would require $4 \\times 10^{12}$ bytes of 4 terabytes of memory.\n",
    "\n",
    "The situation really is not as bad as we are making it out to be as long as we take advantage of the sparse nature of the matrices.  In fact for 1 dimensional problems direct methods can be reduced to $\\mathcal{O}(N)$ in the case for a tridiagonal system.  The situation is not so great for higher-dimensional problems however unless most structure can be leveraged.  Examples of these types of solvers include fast Fourier methods such as fast Poisson solvers."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Iterative Methods\n",
    "\n",
    "Iterative methods take another tact that direct methods.  If we have the system $A x = b$ we form an iterative procedure that applies a function, say $L$, such that\n",
    "$$\n",
    "    \\hat{x}^{(k)} = L(\\hat{x}^{(k-1)})\n",
    "$$\n",
    "where we want errot between the real solution $x$ and $\\hat{x}^{(k)}$ goes to zero as $k \\rightarrow \\infty$.  We will explore these methods in the next lecture."
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  },
  "latex_envs": {
   "bibliofile": "biblio.bib",
   "cite_by": "apalike",
   "current_citInitial": 1,
   "eqLabelWithNumbers": true,
   "eqNumInitial": 0
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
